package com.vicent.service.impl;

import com.alibaba.fastjson.JSONObject;
import com.alipay.api.AlipayApiException;
import com.alipay.api.AlipayClient;
import com.alipay.api.request.*;
import com.alipay.api.response.*;
import com.vicent.config.AliPayClientConfig;
import com.vicent.entity.OrderInfo;
import com.vicent.entity.RefundInfo;
import com.vicent.enums.OrderStatus;
import com.vicent.enums.PayType;
import com.vicent.enums.wxpay.AliPayTradeState;
import com.vicent.service.AliPayService;
import com.vicent.service.OrderInfoService;
import com.vicent.service.PaymentInfoService;
import com.vicent.service.RefundInfoService;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;

import javax.annotation.Resource;
import java.math.BigDecimal;
import java.util.Map;
import java.util.Optional;
import java.util.concurrent.locks.ReentrantLock;

/**
 * <p>
 *  ali支付服务实现类
 * </p>
 *
 * @author vincent
 * @since 2023-09-01
 */
@Slf4j
@Service
public class AliPayServiceImpl implements AliPayService {

    @Autowired
    private OrderInfoService orderInfoService;
    @Autowired
    private AlipayClient alipayClient;
    @Autowired
    private AliPayClientConfig aliPayClientConfig;
    @Resource
    private PaymentInfoService paymentInfoService;

    @Resource
    private RefundInfoService refundsInfoService;

    private final ReentrantLock lock = new ReentrantLock();

    @Override
    @Transactional(rollbackFor = Exception.class)
    public String treadeCreate(Long productId) {
        log.info("创建订单开始，productId:{}", productId);
        try {
            //创建订单（这里只演示简单创建订单的过程，不涉及用户，实际创建订单流程比较复杂）
            OrderInfo orderInfo = orderInfoService.createOrderByProductId(productId, PayType.ALIPAY.getType());
            //组装下单请求
            AlipayTradePagePayRequest request = new AlipayTradePagePayRequest();
            //异步接收地址，仅支持http/https，公网可访问
            request.setNotifyUrl(aliPayClientConfig.getNotifyUrl());
            //同步跳转地址，仅支持http/https
            request.setReturnUrl(aliPayClientConfig.getReturnUrl());
            /******必传参数******/
            JSONObject bizContent = new JSONObject();
            //商户订单号，商家自定义，保持唯一性（64个字符以内，仅支持字母、数字、下划线且需保证在商户端不重复）
            bizContent.put("out_trade_no", orderInfo.getOrderNo());
            //支付金额，最小值0.01元(订单总金额，单位为元，精确到小数点后两位，取值范围为 [0.01,100000000]。金额不能为0)
            bizContent.put("total_amount", BigDecimal.valueOf(orderInfo.getTotalFee()).divide(new BigDecimal("100")));
            //订单标题，不可使用特殊符号
            bizContent.put("subject", orderInfo.getTitle());
            //销售产品码，与支付宝签约的产品码名称。注：目前电脑支付场景下仅支持FAST_INSTANT_TRADE_PAY
            bizContent.put("product_code", "FAST_INSTANT_TRADE_PAY");
            request.setBizContent(bizContent.toString());
            AlipayTradePagePayResponse response = alipayClient.pageExecute(request);
            //todo:支付响应记录日志
            if (response.isSuccess()) {
                log.info("调用支付成功，返回结果:{}",response.getBody());
                return response.getBody();
            }else{
                log.error("调用支付失败，返回码：{}，返回描述：{}",response.getCode(),response.getMsg());
                throw new RuntimeException("创建支付交易失败");
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new RuntimeException("创建支付交易失败");
        }
    }
    /**
     * 处理订单
     * @param params
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void processOrder(Map<String, String> params) {
        log.info("支付异步通知触发订单状态的修改");
        //获取订单号
        String orderNo = params.get("out_trade_no");
        /*在对业务数据进行状态检查和处理之前，要采用数据锁进行并发控制，以避免函数重入造成的数据混乱*/
        //尝试获取锁：成功获取则立即返回true，获取失败则立即返回false。不必一直等待锁的释放
        if (lock.tryLock()) {
            try {
                //处理重复通知
                //接口调用的幂等性：无论接口被调用多少次，以下业务执行一次
                String orderStatus = orderInfoService.getOrderStatus(orderNo);
                if (!OrderStatus.NOTPAY.getType().equals(orderStatus)) {
                    return;
                }
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.SUCCESS);
                //记录支付日志
                paymentInfoService.createPaymentInfoForAliPay(params);
            } finally {
                //要主动释放锁
                lock.unlock();
            }
        }
    }

    @Override
    public void cancelOrder(String orderNo) {
        log.info("关闭订单开始,order:{}",orderNo);
        //检验订单是否存在
        String orderStatus = Optional.ofNullable(orderInfoService.getOrderByOrderNo(orderNo)).map(m -> m.getOrderStatus()).orElseThrow(() -> new RuntimeException("订单不存在"));
        if (!orderStatus.equals("未支付")) {
            throw new RuntimeException("只有已支付的订单才可以关单");
        }
        //组装支付宝关单请求对象
        this.closeOrder(orderNo);
        //更新订单状态为已关闭(使用异常通信，如果调用支付宝进行取消订单的时候抛出任何异常，后续订单都不会更新)
        orderInfoService.updateStatusByOrderNo(orderNo,OrderStatus.CANCEL);

    }
    public void closeOrder(String orderNo) {
        try {
            AlipayTradeCloseRequest request = new AlipayTradeCloseRequest();
            JSONObject bizContent = new JSONObject();
            bizContent.put("trade_no", orderNo);
            request.setBizContent(bizContent.toString());
            AlipayTradeCloseResponse response = alipayClient.execute(request);
            log.info("调用支付宝订单:{}关单接口:{}",orderNo,response);
            if (response.isSuccess()) {
                log.info("调用支付宝订单:{}关单成功!",orderNo);
            }else{
//                throw new RuntimeException("调用支付宝关单接口失败，orderNo:"+orderNo);
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new RuntimeException("调用支付宝关单接口失败，orderNo:"+orderNo);
        }
    }

    /**
     * 查询订单
     * @param orderNo
     * @return 返回订单查询结果，如果返回null则表示支付宝端尚未创建订单
     */
    @Override
    public String queryOrder(String orderNo) {
        log.info("查询订单开始,order:{}",orderNo);
        //检验订单是否存在
        if (orderInfoService.getOrderByOrderNo(orderNo)==null) {
            throw new RuntimeException("订单不存在");
        }
        try {
            //组装支付宝查询订单请求对象
            AlipayTradeQueryRequest request = new AlipayTradeQueryRequest();
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", orderNo);
            request.setBizContent(bizContent.toString());
            AlipayTradeQueryResponse response = alipayClient.execute(request);
            log.info("调用支付宝查询订单返回结果:{}",response);
            if (response.isSuccess()) {
                log.info("调用支付宝查询订单成功!",orderNo);
                return response.getBody();
            }else{
                log.info("调用失败，返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
                //throw new RuntimeException("查单接口的调用失败");
                return null;//订单不存在
            }

        } catch (AlipayApiException e) {
            e.printStackTrace();
        }
        throw new RuntimeException("查单接口的调用失败");
    }

    /**
     * 退款
     * @param orderNo
     * @param reason
     */
    @Transactional(rollbackFor = Exception.class)
    @Override
    public void refund(String orderNo, String reason) {
        try {
            log.info("调用退款API");
            //创建退款单
            RefundInfo refundInfo = refundsInfoService.createRefundByOrderNoForAliPay(orderNo, reason);
            //调用统一收单交易退款接口
            AlipayTradeRefundRequest request = new AlipayTradeRefundRequest();
            //组装当前业务方法的请求参数
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", orderNo);//订单编号
            BigDecimal refund = new BigDecimal(refundInfo.getRefund().toString()).divide(new BigDecimal("100"));
            //BigDecimal refund = new BigDecimal("2").divide(new BigDecimal("100"));
            bizContent.put("refund_amount", refund);//退款金额：不能大于支付金额
            bizContent.put("refund_reason", reason);//退款原因(可选)
            request.setBizContent(bizContent.toString());
            //执行请求，调用支付宝接口
            AlipayTradeRefundResponse response = alipayClient.execute(request);
            if (response.isSuccess()) {
                log.info("调用成功，返回结果 ===> " + response.getBody());
                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_SUCCESS);
                //更新退款单
                refundsInfoService.updateRefundForAliPay(
                        refundInfo.getRefundNo(),
                        response.getBody(),
                        AliPayTradeState.REFUND_SUCCESS.getType()); //退款成功

            } else {
                log.info("调用失败，返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());

                //更新订单状态
                orderInfoService.updateStatusByOrderNo(orderNo, OrderStatus.REFUND_ABNORMAL);

                //更新退款单
                refundsInfoService.updateRefundForAliPay(
                        refundInfo.getRefundNo(),
                        response.getBody(),
                        AliPayTradeState.REFUND_ERROR.getType()); //退款失败
            }

        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new RuntimeException("创建退款申请失败");
        }
    }

    /**
     * 查询退款
     * @param orderNo
     * @return
     */
    @Override
    public String queryRefund(String orderNo) {
        try {
            log.info("查询退款接口调用 ===> {}", orderNo);
            AlipayTradeFastpayRefundQueryRequest request = new AlipayTradeFastpayRefundQueryRequest();
            JSONObject bizContent = new JSONObject();
            bizContent.put("out_trade_no", orderNo);
            bizContent.put("out_request_no", orderNo);
            request.setBizContent(bizContent.toString());
            AlipayTradeFastpayRefundQueryResponse response = alipayClient.execute(request);
            if (response.isSuccess()) {
                log.info("调用成功，返回结果 ===> " + response.getBody());
                return response.getBody();
            } else {
                log.info("调用失败，返回码 ===> " + response.getCode() + ", 返回描述 ===> " + response.getMsg());
                //throw new RuntimeException("查单接口的调用失败");
                return null;//订单不存在
            }
        } catch (AlipayApiException e) {
            e.printStackTrace();
            throw new RuntimeException("查单接口的调用失败");
        }
    }
}
